/* ----------------------------------------------------------------------------
* File: skype_service.cpp
*
* Desc: Windows service stuffs
*
* Created: 20/12/2010, by Alexey Korshunov
* ---------------------------------------------------------------------------- */
#include <WinError.h>

#include "api_command_dispatcher.h"
#include "api_event_dispatcher.h"
#include "attach_handler_impl.h"
#include "command_handler_impl.h"
#include "events.h"
#include "logger.h"
#include "logger_impl.h"
#include "main.h"
#include "message_queue_impl.h"
#include "module_controller.h"
#include "quiz_dictionary_impl.h"
#include "quiz_module.h"
#include "quiz_user_profile_impl.h"

using namespace SKYPE4COMLib;
using namespace std;

SkypeEnv Env;
LPWSTR serviceName = TEXT("Skype Plugin");


void DeployChatInfoModule(ModuleManagement::ModuleController* controller);
void DeployQuizModule(ModuleManagement::ModuleController* controller);
void DeployTalkModule(ModuleManagement::ModuleController* controller);


/************************************************************************/
/* Install our service                                                  */
/************************************************************************/
int InstallWinService(TCHAR *servicePath)
{
	int ret = SKYPE_PLUG_OK;

#ifdef _DEBUG
	MyOutputDebugString("SkypePlugin: InstallWinService() - begin");
#endif

	// connection to the SCM (Service Control Manager)
	SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
	if (hSCManager == NULL)
	{
		MyOutputDebugString("SkypePlugin: InstallWinService() ERROR - Unable to connect to the SCM, error - %d", GetLastError());
		return SKYPE_PLUG_ERROR;
	}

	// Create Skype plugin as windows service
	if (CreateSkypeService(hSCManager) != SKYPE_PLUG_OK)
	{
		MyOutputDebugString("SkypePlugin: InstallWinService() ERROR - Unable to install service");
		ret = SKYPE_PLUG_ERROR;
	}
	else
	{
#ifdef _DEBUG
		MyOutputDebugString("SkypePlugin: InstallWinService() - Service installed successfully");
#endif
	}

	// close handle
	CloseServiceHandle(hSCManager);

#ifdef _DEBUG
	MyOutputDebugString("SkypePlugin: InstallWinService() - end");
#endif

	return ret;
}

/************************************************************************/
/* Remove our service                                                   */
/************************************************************************/
int RemoveWinService(TCHAR *servicePath)
{
	int ret = SKYPE_PLUG_OK;

#ifdef _DEBUG
	MyOutputDebugString("SkypePlugin: RemoveWinService() - begin");
#endif

	// connect to SCM
	SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
	if (hSCManager == NULL)
	{
		MyOutputDebugString("SkypePlugin: RemoveWinService() ERROR - Unable to connect to the SCM, error - %d", GetLastError());
		return SKYPE_PLUG_ERROR;
	}

	// open service
	SC_HANDLE hService = OpenService(hSCManager, serviceName, SERVICE_STOP | DELETE);
	if (hService == NULL)
	{
		CloseServiceHandle(hSCManager);
		MyOutputDebugString("SkypePlugin: RemoveWinService() ERROR - Can't open service, error - %d", GetLastError());
		return SKYPE_PLUG_ERROR;
	}

	// delete service
	if (!DeleteService(hService))
	{
		MyOutputDebugString("SkypePlugin: RemoveWinService() ERROR - Can't remove service, error - %d", GetLastError());
		ret = SKYPE_PLUG_ERROR;
	}
	else
	{
#ifdef _DEBUG
		MyOutputDebugString("SkypePlugin: RemoveWinService() - Service removed successfully");
#endif
	}

	// close handles
	CloseServiceHandle(hService);
	CloseServiceHandle(hSCManager);

#ifdef _DEBUG
	MyOutputDebugString("SkypePlugin: RemoveWinService() - end");
#endif
	return ret;
}

/************************************************************************/
/* Start our service                                                    */
/************************************************************************/
int StartWinService(TCHAR *servicePath)
{
	int ret = SKYPE_PLUG_OK;

#ifdef _DEBUG
	MyOutputDebugString("SkypePlugin: StartWinService() - begin");
#endif

	// connection to the SCM (Service Control Manager)
	SC_HANDLE hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
	if (hSCManager == NULL)
	{
		MyOutputDebugString("SkypePlugin: StartSkypeService() ERROR - Unable to connect to the SCM, error - %d", GetLastError());
		return SKYPE_PLUG_ERROR;
	}

	// Try to start service
	ret = StartSkypeService(hSCManager);

	CloseServiceHandle(hSCManager);

#ifdef _DEBUG
	MyOutputDebugString("SkypePlugin: StartWinService() - end");
#endif

	return ret;
}

/************************************************************************/
/* Stop our service                                                    */
/************************************************************************/
int StopWinService(TCHAR *servicePath)
{
	int ret = SKYPE_PLUG_OK;
	//// Fill in the SERVICE_STATUS structure.
	//Env.serviceStatus.dwCurrentState = SERVICE_STOPPED;
	//Env.serviceStatus.dwWin32ExitCode = NO_ERROR;
	//Env.serviceStatus.dwWaitHint = 0;
	//Env.serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
	//Env.serviceStatus.dwCheckPoint = 0;

	//// Report the status of the service to the SCM.
	//SetServiceStatus(Env.hStatus, &Env.serviceStatus);

	SERVICE_STATUS strServiceStatus;
	SC_HANDLE      schManager;
	SC_HANDLE      schService;
	char           *pszServiceName;
	DWORD          dwError;

	// Open SCN manager
	if ((schManager = OpenSCManager(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS)) == NULL)
	{
		MyOutputDebugString("SkypePlugin: StopWinService() ERROR - Unable to open SCM manager, error - %d", GetLastError());
		return FALSE;
	}

#ifdef _DEBUG
	MyOutputDebugString("SkypePlugin: StopWinService() - SCM manager is opened");
#endif

	// Open service
	if ((schService = OpenService( schManager, serviceName, SERVICE_STOP)) == NULL )
	{
		MyOutputDebugString("SkypePlugin: StopWinService() ERROR - Unable to open service, error - %d", GetLastError());
		CloseServiceHandle(schManager);
		return FALSE;
	}

#ifdef _DEBUG
	MyOutputDebugString("SkypePlugin: StopWinService() - Service is opened");
#endif

	// Stop service
	if (ControlService(schService, SERVICE_CONTROL_STOP, &strServiceStatus) != 0)
	{
		switch(dwError = GetLastError())
		{
		case ERROR_ACCESS_DENIED:
		case ERROR_DEPENDENT_SERVICES_RUNNING:
		case ERROR_INVALID_HANDLE:
		case ERROR_INVALID_PARAMETER:
		case ERROR_INVALID_SERVICE_CONTROL:
		case ERROR_SERVICE_CANNOT_ACCEPT_CTRL:
		case ERROR_SERVICE_REQUEST_TIMEOUT:
		default:
			MyOutputDebugString("SkypePlugin: dwError = %d", dwError);
			CloseServiceHandle(schService);
			CloseServiceHandle(schManager);
			return FALSE;
		case NO_ERROR:
		case ERROR_SERVICE_NOT_ACTIVE:
		case ERROR_SHUTDOWN_IN_PROGRESS:
			MyOutputDebugString("SkypePlugin: dwError = %d", dwError);
			CloseServiceHandle(schService);
			CloseServiceHandle(schManager);
			return TRUE;
		}
	}

	// Check status of service
	if (strServiceStatus.dwCurrentState != SERVICE_STOPPED)
	{
		dwError = GetLastError();
		MyOutputDebugString("SkypePlugin: StopWinService() ERROR - Unable to stop service, dwError = %d", dwError);
		ret = SKYPE_PLUG_ERROR;
	}
	else
		MyOutputDebugString("SkypePlugin: StopWinService() - Service is stopped");

	CloseServiceHandle(schService);
	CloseServiceHandle(schManager);
	return SKYPE_PLUG_OK;
}

/************************************************************************/
/* Handler for control status requests                                  */
/************************************************************************/
void ControlHandler(DWORD request)
{
	switch (request)
	{
	case SERVICE_CONTROL_STOP:
		Env.serviceStatus.dwCurrentState = SERVICE_STOPPED;
		Env.serviceStatus.dwWin32ExitCode = 0;
		break;

	case SERVICE_CONTROL_SHUTDOWN:
		Env.serviceStatus.dwCurrentState = SERVICE_STOPPED;
		Env.serviceStatus.dwWin32ExitCode = 0;
		break;

	default:
		break;
	}

	SetServiceStatus(Env.hStatus, &Env.serviceStatus);
	return;
}

/************************************************************************/
/* Main function for service                                            */
/************************************************************************/
void ServiceMain(int argc, char** argv)
{
	ISkypePtr pSkype = NULL;
	SkypeEvents::EventDispatcher DispEvent;

#ifdef _DEBUG
	MyOutputDebugString("SkypePlugin: ServiceMain() - start");
#endif
	
	try
	{
		Env.serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
		Env.serviceStatus.dwServiceSpecificExitCode = 0;
		Env.serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;

		// register handler for controlling requests
		Env.hStatus = RegisterServiceCtrlHandler(serviceName, (LPHANDLER_FUNCTION)ControlHandler);
		if (Env.hStatus == (SERVICE_STATUS_HANDLE)0)
			throw Common::Exception("ServiceMain() ERROR - Can't register control handler");

		// Report about start pending
		ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 0);

		// Init service
		ServiceInit();
	}
	catch(Common::Exception& ex)
	{
		MyOutputDebugString("Exception: %s", ex.GetStr().c_str());
	}
	catch(...)
	{
		//MyOutputDebugString(TEXT("Unexpected exception"));
	}

#ifdef _DEBUG
	MyOutputDebugString("SkypePlugin: ServiceMain() - stop");
#endif

	// Report about stopped state
	ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);

	return;
}

void ReportSvcStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint)
{
	static DWORD dwCheckPoint = 1;

	Env.serviceStatus.dwCurrentState = dwCurrentState;
	Env.serviceStatus.dwWin32ExitCode = dwWin32ExitCode;
	Env.serviceStatus.dwWaitHint = dwWaitHint;

	if (dwCurrentState == SERVICE_START_PENDING)
		Env.serviceStatus.dwControlsAccepted = 0;
	else 
		Env.serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;

	if ((dwCurrentState == SERVICE_RUNNING) ||
		(dwCurrentState == SERVICE_STOPPED) )
		Env.serviceStatus.dwCheckPoint = 0;
	else 
		Env.serviceStatus.dwCheckPoint = dwCheckPoint++;

	// Report the status of the service to the SCM.
	SetServiceStatus(Env.hStatus, &Env.serviceStatus);
}

void ServiceInit()
{
	// Report about running state
	ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0);

	// Create module controller
	std::auto_ptr<ModuleManagement::ModuleController> controller = ModuleManagement::CreateModuleController();
	if (!controller.get())
		throw Common::Exception("ServiceInit: Unable to create module controller");
	MyOutputDebugString("SkypePlugin: ServiceMain() - Module controller is created");

	// Attach handler
	using namespace SkypePlugin;
	std::auto_ptr<AttachHandlerImpl> attachHandler(new AttachHandlerImpl(Env.hStatus));
	if (!attachHandler.get())
		throw Common::Exception("ServiceInit: Unable to create attach handler");
	MyOutputDebugString("SkypePlugin: ServiceMain() - attach handler is created");

	// Command handler
	using namespace SkypePlugin;
	std::auto_ptr<APICommandHandlerImpl> commandHandler(new APICommandHandlerImpl());
	if (!commandHandler.get())
		throw Common::Exception("ServiceInit: Unable to create API command handler");
	APICommandHandlerParams params;
	params.Controller = controller.get();
	params.ServiceHandle = Env.hStatus;
	commandHandler->Init(params);
	MyOutputDebugString("SkypePlugin: ServiceMain() - command handler is created");

	// API command dispatcher
	using namespace SkypeProtection;
	APICommandDispatcherParams dispParams;
	dispParams.AttachHandler = attachHandler.release();
	dispParams.CommandHandler = commandHandler.release();
	std::auto_ptr<APICommandDispatcher> dispatcher(CreateAPICommandDispatcher(dispParams));
	if (!dispatcher.get())
		throw Common::Exception("ServiceInit: Unable to create API command dispatcher");
	MyOutputDebugString("SkypePlugin: ServiceMain() - API command dispatcher is created (OK)");

	// Main cycle
	MSG msg;
	while(Env.serviceStatus.dwCurrentState == SERVICE_RUNNING && !Env.bError &&
		GetMessage(&msg, NULL, 0, 0 ))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
}


/************************************************************************/
/* Start Skype plugin as service                                        */
/************************************************************************/
int StartSkypeService(SC_HANDLE hSCManager)
{
	SERVICE_STATUS_PROCESS ssStatusProcess;
	SERVICE_STATUS serviceStatus;
	DWORD dwOldCheckPoint;
	DWORD dwStartTickCount;
	DWORD dwWaitTime;
	DWORD dwBytesNeeded;

	// open service
	SC_HANDLE hService = OpenService(hSCManager, serviceName, SERVICE_ALL_ACCESS);
	if (hService == NULL)
	{
		MyOutputDebugString("SkypePlugin: StartWinService() ERROR - Can't open service");
		return SKYPE_PLUG_ERROR;
	}

#ifdef _DEBUG
	MyOutputDebugString("SkypePlugin: Service is opened");
#endif

	// start service
	if(!StartService(hService, 0, NULL)) 
	{
		DWORD dwErr = GetLastError();
		switch(dwErr) 
		{
		case ERROR_ACCESS_DENIED:
			MyOutputDebugString("skype_plugin: StartWinService() ERROR_ACCESS_DENIED");
			break;

		case ERROR_INVALID_HANDLE:
			MyOutputDebugString("skype_plugin: StartWinService() ERROR_INVALID_HANDLE");
			break;

		case ERROR_PATH_NOT_FOUND:
			MyOutputDebugString("SkypePlugin: StartSkypeService() ERROR_PATH_NOT_FOUND");
			break;

		case ERROR_SERVICE_ALREADY_RUNNING:
			MyOutputDebugString("SkypePlugin: StartSkypeService() ERROR_SERVICE_ALREADY_RUNNING");
			break;

		case ERROR_SERVICE_DATABASE_LOCKED:
			MyOutputDebugString("SkypePlugin: StartSkypeService() ERROR_SERVICE_DATABASE_LOCKED");
			break;

		case ERROR_SERVICE_DEPENDENCY_DELETED:
			MyOutputDebugString("SkypePlugin: StartSkypeService() ERROR_SERVICE_DEPENDENCY_DELETED");
			break;

		case ERROR_SERVICE_DEPENDENCY_FAIL:
			MyOutputDebugString("SkypePlugin: StartSkypeService() ERROR_SERVICE_DEPENDENCY_FAIL");
			break;

		case ERROR_SERVICE_DISABLED:
			MyOutputDebugString("SkypePlugin: StartSkypeService() ERROR_SERVICE_DISABLED");
			break;

		case ERROR_SERVICE_LOGON_FAILED:
			MyOutputDebugString("SkypePlugin: StartSkypeService() ERROR_SERVICE_LOGON_FAILED");
			break;

		case ERROR_SERVICE_MARKED_FOR_DELETE:
			MyOutputDebugString("SkypePlugin: StartSkypeService() ERROR_SERVICE_MARKED_FOR_DELETE");
			break;

		case ERROR_SERVICE_NO_THREAD:
			MyOutputDebugString("SkypePlugin: StartSkypeService() ERROR_SERVICE_NO_THREAD");
			break;

		case ERROR_SERVICE_REQUEST_TIMEOUT:
			MyOutputDebugString("SkypePlugin: StartSkypeService() ERROR_SERVICE_REQUEST_TIMEOUT");
			break;

		default:
			MyOutputDebugString("skype_plugin: StartWinService() Undefined");
			break;
		}

		MyOutputDebugString("skype_plugin: StartWinService() ERROR - Can't start service");
		return SKYPE_PLUG_ERROR;
	}

#ifdef _DEBUG
	MyOutputDebugString("SkypePlugin: Service is started");
#endif

	// Check the status until the service is no longer start pending
	if (!QueryServiceStatusEx(hService, SC_STATUS_PROCESS_INFO, (LPBYTE)&ssStatusProcess, sizeof(SERVICE_STATUS_PROCESS), &dwBytesNeeded ))
	{
		MyOutputDebugString("skype_plugin: StartWinService() - Unable to query service status");
		return SKYPE_PLUG_ERROR;
	}

	// Save the tick count and initial checkpoint
	dwStartTickCount = GetTickCount();
	dwOldCheckPoint = ssStatusProcess.dwCheckPoint;

	while(ssStatusProcess.dwCurrentState == SERVICE_START_PENDING)
	{
		// Do not wait longer than the wait hint. A good interval is 
		// one tenth the wait hint, but no less than 1 second and no 
		// more than 10 seconds. 

		dwWaitTime = ssStatusProcess.dwWaitHint / 10;

		if( dwWaitTime < 1000 )
			dwWaitTime = 1000;
		else if ( dwWaitTime > 10000 )
			dwWaitTime = 10000;

		Sleep(dwWaitTime);

		// Check the status again. 
		if (!QueryServiceStatusEx( 
			hService,             // handle to service 
			SC_STATUS_PROCESS_INFO, // info level
			(LPBYTE)&ssStatusProcess,              // address of structure
			sizeof(SERVICE_STATUS_PROCESS), // size of structure
			&dwBytesNeeded ) )              // if buffer too small
			break; 

		if (ssStatusProcess.dwCheckPoint > dwOldCheckPoint)
		{
			// The service is making progress.
			dwStartTickCount = GetTickCount();
			dwOldCheckPoint = ssStatusProcess.dwCheckPoint;
		}
		else
		{
			if(GetTickCount() - dwStartTickCount > ssStatusProcess.dwWaitHint)
			{
				// No progress made within the wait hint
				break;
			}
		}
	}

	CloseServiceHandle(hService);

	if (ssStatusProcess.dwCurrentState == SERVICE_RUNNING)
	{
		MyOutputDebugString("skype_plugin: StartWinService() - Service started successfully");
		return SKYPE_PLUG_OK;
	}
	else
	{
		MyOutputDebugString("skype_plugin: ERROR - Service not started");
		return SKYPE_PLUG_ERROR;
	}
}


/************************************************************************/
/* Create Skype service                                                 */
/************************************************************************/
int CreateSkypeService(SC_HANDLE hSCManager)
{
	TCHAR szPath[MAX_PATH];
	DWORD dwErr;

	// Get file name
	if (!GetModuleFileName(NULL, szPath, MAX_PATH))
	{
		MyOutputDebugString("skype_plugin: InstallSkypeService() ERROR - Unable to get module name");
		return SKYPE_PLUG_ERROR;
	}

	// Create service
	SC_HANDLE hService = CreateService(
		hSCManager,					// SCManager database 
		serviceName,				// name of service 
		serviceName,           // service name to display 
		SERVICE_ALL_ACCESS,        // desired access 
		SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS, // service type 
		SERVICE_DEMAND_START,      // start type 
		SERVICE_ERROR_NORMAL,      // error control type 
		szPath,                    // path to service's binary 
		NULL,                      // no load ordering group 
		NULL,                      // no tag identifier 
		NULL,                      // no dependencies 
		NULL,                      // LocalSystem account 
		NULL);                     // no password 

	if (hService == NULL)
	{
		dwErr = GetLastError();
		if (dwErr == ERROR_SERVICE_EXISTS)
		{
			MyOutputDebugString("skype_plugin: InstallSkypeService() - Service already exists");
		}
		else
		{
			MyOutputDebugString("skype_plugin: InstallSkypeService() ERROR - Unable to create service");
			return SKYPE_PLUG_ERROR;
		}
	}

	CloseServiceHandle(hService);
	return SKYPE_PLUG_OK;
}